package bosVM2.Engine.Frames;

import java.util.HashMap;
import java.util.Stack;

import bosVM2.Engine.ExecutionEngine;
import bosVM2.Engine.Heap;
import bosVM2.FaultHandling.AnCodeFaultHandler;
import bosVM2.FaultHandling.HamDistanceFaultHandler;
import bosVM2.meta.LookUp;
import bosVM2.meta.LookUp.Type;
import bosVM2.meta.LookUp.aritOp;
import bosVM2.meta.LookUp.ifOp;
import bosVM2.meta.LookUp.opCode;
import bosVM2.meta.Objects.BosMethod;

public class MethodFrame implements Frame {
	 
	//Globals
	private Heap H = Heap.getH();
	private ExecutionEngine e;
	
	//Frame variables <m,pc,lv,os>
	private BosMethod m;
	private int[] pc;
	private HashMap<String,Object> lv;
	private Stack<Object> os;
	
	//FaultHandling
	HamDistanceFaultHandler ham = new HamDistanceFaultHandler();
	AnCodeFaultHandler an = AnCodeFaultHandler.getAnFaultHAndler();
	
	//Constructor
	public MethodFrame(BosMethod m, Integer ref, ExecutionEngine e) 
	{
		this.m = new BosMethod(m);
		this.pc = new int[]{0,0};
		this.lv = new HashMap<String,Object>();
		lv.put("this", ref);
		this.os = new Stack<Object>();
		this.e = e;
	}

	//frame variable Getters, setters, and util methods
	public BosMethod getMethod() {
		return m;
	}
	
	public int[] getpcArray() {
		return pc;
	}
	
	public int getpc() {
		if(pc[0] == 0 && pc[1] == 0) { return 0;}
		else {
			if(pc[0] != an.deCode(pc[1]) || (pc[1]%3 != 0 && pc[0] == an.deCode(pc[1])))
			{
				return setpc(an.handleValueFault(pc[0], pc[1]));
			}
			else
			{
				return pc[0];
			}
		}
	}
	
	private void pcInc()
	{
		pc[0]++;
		pc[1] = pc[1] + an.enCode(1);
	}
	
	//returns the new pc
	private int setpc(int pc)
	{
		this.pc[0] = pc;
		this.pc[1] = an.enCode(pc);
		return pc;
	}
	
	public HashMap<String,Object> getlv() {
		return lv;
	}
	
	public Stack<Object> getOs() {
		return os;
	}
	
	public ExecutionEngine getEngine()
	{
		return e;
	}
	
	//TODO use thisjoinpoint.getThis to return the object instead
	public MethodFrame getMethodFrame()
	{
		return this;
	}
	
	//Execution
	@Override
	public Object executeFrame() {
		while(!(m.instructionAt(getpc()) == null))
		{ 
			//AspectJ hack: Makes fault injection possible
			getMethodFrame();
			
			Object[] o = m.instructionAt(getpc());
			opCode op = opCode.getOpcode((Integer)o[0]);
			switch(op)
			{
			//CORE
			case NOP: pcInc(); break;
			case POP: doPop((Integer) o[1]); break;
			case PUSH: doPush(o[2]); break;
			case GOTO: setpc((Integer) o[1]); break;
			case ARIT: doArit((Integer)o[1]); break;
			case IF:  doIf((Integer)o[1], (Integer) o[2]); break;
			case LOAD: doLoad((Integer)o[1],(String) o[2]); break;
			case STORE: doStore((Integer)o[1], (String) o[2]); break;
			//Methods
			case INVOKEVIRTUAL: doInvokeVirtual(o); break;
			case RETURN: return pop();
			//Objects
			case NEW: doNew((String) o[1]); break;
			case PUTFIELD: doPutField(o); break;
			case GETFIELD: doGetField(o); break;
			case THROW: System.err.println("Throw: not yet implemented"); break;
			case UNREC: 
				System.err.println("error detected in OpCode");
				ham.handleOpcodeFault(getpc(),m,e);
				break;
			}
		}
		return pop();
	}

	//Opcode methods
	private void doGetField(Object[] o) {
		push(H.get((Integer) pop()).getField((String) o[1]));
		pcInc();
	}

	private void doPutField(Object[] o) {
		H.get((Integer) pop()).putfield((String) o[1], pop());
		pcInc();
	}

	private void doNew(String clss) {
		int i = H.Allocate(e.getMeta().getMetaClass(clss));
		push(i);	
		pcInc();
	}

	private void doInvokeVirtual(Object[] o) {
		String clss = ((String) o[1]).substring(0,((String) o[1]).lastIndexOf("."));
		String meth =((String) o[1]).substring(((String) o[1]).lastIndexOf(".")+1);
		BosMethod im = e.getMeta().getMetaClass(clss).getMethod(meth);
		int ref = (Integer) pop(); //Class
		MethodFrame frame = new MethodFrame(im,ref,e);
		for(String s: im.getArgs())
		{
			frame.lv.put(s, pop());
		}
		if(o[1] == Type.VOID) {frame.executeFrame(); } //FIXME this is no longer working, fix before making void testing
		else { push(frame.executeFrame()); }
		pcInc();	
	}

	//TODO Consider error correction on LV Values?
	private void doStore(int type, String addr) {
		lv.put(addr, pop());	
		pcInc();
	}

	//TODO Consider error correction on Values??
	private void doLoad(int type, String addr) {
		push(lv.get(addr));
		pcInc();	
	}

	private void doIf(int op, Integer i) {
		ifOp ifOp = LookUp.ifOp.getIfOp(op);
		switch(ifOp)
		{
		case EQUALS: 
			if(((Integer) os.pop()).equals((Integer) os.pop())) { setpc(i);}
			else {pcInc();} break;
		case GRTH:
			if((Integer) pop() > (Integer) pop()) { setpc(i);}
			else {pcInc();} break;
		case LSTH:
			if((Integer) pop() < (Integer) pop()) { setpc(i);}
			else {pcInc();} break;
		case NEQUALS:
			if((Integer)pop() != (Integer)pop()) { setpc(i);}
			else {pcInc();} break;
		case UNREC: System.err.println("Error detected in If operation");
				 ham.handleIfcodeFault(getpc(), m, e);
		}	
	}

	private void doArit(int arit) {
		aritOp o = LookUp.aritOp.getArit(arit);
		switch(o)
		{
			case ADD: 
				doAdd();	
				pcInc();
				break;
			case DIV: 
				doDiv();
				pcInc();
				break;
			case MUL: 
				doMul();
				pcInc();
				break;
			case SUB: 
				doSub();	
				pcInc();
				break;
			case UNREC: System.err.println("Error detected in Arit Operation");
					 ham.handleAritcodeFault(getpc(), m, e);					 
		}		
	}
	
	private void doPush(Object v) {
		push(v);
		pcInc();
	}

	private void doPop(int i) {
		for(int j = 0; j < i; j++)
		{ pop();}
		pcInc();
	}
	
	//Private utility and functionality methods
	private void doAdd() {
		int v1,v2,vc1,vc2,r;
		v1 = pop();
		v2 = pop();
		vc1 = an.enCode(v1);
		vc2 = an.enCode(v2);
		
		r = vc1+vc2;
		if(!an.valid(r))
		{
			push(v1+v2);
		} else push(an.deCode(r));	
	}
	
	private void doSub() {
		int v1,v2,vc1,vc2,r;
		v1 = pop();
		v2 = pop();
		vc1 = an.enCode(v1);
		vc2 = an.enCode(v2);
		
		r = vc1-vc2;
		if(!an.valid(r))
		{
			push(v1-v2);
		} else push(an.deCode(r));	
	}
	
	private void doMul() {
		int v1,v2,vc1,vc2,r;
		v1 = pop();
		v2 = pop();
		vc1 = an.enCode(v1);
		vc2 = an.enCode(v2);
		
		r = vc1*vc2;
		if(!an.valid(r))
		{
			push(v1*v2);
		} else push(an.deCode(r));		
	}
	
	private void doDiv() {
		int v1,v2,vc1,vc2,r;
		v1 = pop();
		v2 = pop();
		vc1 = an.enCode(v1);
		vc2 = an.enCode(v2);
		
		r = vc1/vc2;
		if(!an.valid(r))
		{
			push(v1/v2);
		} else push(an.deCode(r));		
	}

	private void push(Object a)
	{
		os.push(an.enCode((Integer) a ));
		os.push(a);
	}
	
	private int pop()
	{
		int orig = (Integer) os.pop();
		int coded = (Integer) os.pop();
		if(orig != an.deCode(coded))
		{
			return an.handleValueFault(orig, coded);
		}
		else
		{
			return orig;
		}	
	}
}